package ui;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
import physics.Assembly;
import physics.EnvironmentSingleton.EnvironmentalForce;
import physics.Simulation;
import physics.component.Bar;
import physics.component.FixedMass;
import physics.component.Mass;
import physics.component.Muscle;
import physics.component.Spring;
import physics.globalforce.CenterOfMass;
import physics.globalforce.Gravity;
import physics.globalforce.Viscosity;
import physics.globalforce.Wall;


/**
 * This class implements the factory design pattern to load in a configuration
 * file for a spring-mass model. It then adds each component to the simulation,
 * with each component being either a Mass, FixedMass, Spring, or Bar.
 *
 * @author Robert C. Duvall. Modified by Kevin Gao, Connor Gordon
 *
 */
public class Factory {

    private Assembly myCurrentModel = null;

    /**
     * Loads model from data file.
     *
     * @param sim Simulation as context
     * @param modelFile File to read from
     */
    public void loadModel (Simulation sim, File modelFile) {
        try {
            Scanner input = new Scanner(modelFile);
            myCurrentModel = sim.createAndGetNewAssembly();
            while (input.hasNext()) {
                Scanner line = new Scanner(input.nextLine());
                if (line.hasNext()) {
                    String type = line.next();
                    if ("mass".equals(type)) {
                        myCurrentModel.add(massCommand(line));
                    }
                    else if ("spring".equals(type) || "muscle".equals(type)) {
                        myCurrentModel.add(springCommand(line, sim));
                    }
                }
            }
            input.close();
        }
        catch (FileNotFoundException e) {
            // should not happen because File came from user selection
            e.printStackTrace();
        }
    }

    /**
     * Loads environment defaults.
     *
     * @param sim Simulation as context
     */
    public void loadGlobalForces (Simulation sim) {
        sim.clearGlobalForces();
        sim.addGlobalForce(new Gravity());
    }

    /**
     * Loads environment from data file.
     *
     * @param sim Simulation as context
     * @param envFile Data file to read from
     */
    public void loadGlobalForces (Simulation sim, File envFile) {
        sim.clearGlobalForces();
        try {
            // Create default instances of gravity, since it must be there
            Gravity gravityToAdd = new Gravity();

            Scanner input = new Scanner(envFile);
            while (input.hasNext()) {
                Scanner line = new Scanner(input.nextLine());
                if (line.hasNext()) {
                    String type = line.next();
                    if ("gravity".equals(type)) {
                        // Overwrite default gravity if present in new config
                        gravityToAdd.setGlobalGravity(line.nextDouble(),
                                line.nextDouble());
                    }
                    else {
                        environmentCommand(type, line, sim);
                    }

                }
            }
            input.close();

            sim.addGlobalForce(gravityToAdd);
        }
        catch (FileNotFoundException e) {
            // should not happen because File came from user selection
            e.printStackTrace();
        }
    }

    private Mass massCommand (Scanner line) {

        int id = line.nextInt();
        double x = line.nextDouble();
        double y = line.nextDouble();
        double mass = line.nextDouble();
        if (mass < 0) {
            return new FixedMass(id, x, y, -mass);
        }
        else {
            return new Mass(id, x, y, mass);
        }
    }

    private Spring springCommand (Scanner line, Simulation sim) {
        Assembly currentAssembly = sim.getCurrentAssembly();
        int m1 = line.nextInt();
        int m2 = line.nextInt();
        double restLength = line.nextDouble();
        double ks = line.nextDouble();
        // Handle the case if it's a muscle (when amplitude is present)
        if (line.hasNextDouble()) {
            double amplitude = line.nextDouble();
            return new Muscle(currentAssembly.getMass(m1),
                    currentAssembly.getMass(m2), restLength, ks, amplitude);
        }
        // Handle case when it's a bar (negative spring constant) versus normal
        // spring
        if (ks < 0) {
            return new Bar(currentAssembly.getMass(m1),
                currentAssembly.getMass(m2));
        }

        return new Spring(currentAssembly.getMass(m1),
                currentAssembly.getMass(m2), restLength, ks);
    }

    private void environmentCommand(String type, Scanner line, Simulation sim) {
        EnvironmentalForce ef = EnvironmentalForce.valueOf(type.toUpperCase());
        switch(ef) {
            case VISCOSITY:
                sim.addGlobalForce(new Viscosity(line.nextDouble()));
                break;
            case CENTERMASS:
                sim.addGlobalForce(new CenterOfMass(line.nextDouble(),
                        line.nextDouble()));
                break;
            case WALL:
                sim.addGlobalForce(new Wall(line.nextInt(),
                        line.nextDouble(), line.nextDouble(), sim));
                break;
            default:
                break;
        }
    }

}
